package com.nebula.core.dsl.operation;

import com.nebula.core.client.NebulaSession;
import com.nebula.core.dsl.WhereStatement;
import com.nebula.core.dsl.builder.GetSubgraphStatementBuilder;
import com.nebula.core.util.StrFormatter;
import com.vesoft.nebula.client.graph.data.ResultSet;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

import static com.nebula.core.constant.Constant.QUOTED_LITERAL_FORMAT;


/**
 * GET SUBGRAPH 子图查询操作类
 * <p>
 * GET SUBGRAPH [WITH PROP] [<step_count> {STEP|STEPS}] FROM {<vid>, <vid>...}
 * [{IN | OUT | BOTH} <edge_type>, <edge_type>...]
 * [WHERE <expression> [AND <expression> ...]]
 * YIELD [VERTICES AS <vertex_alias>] [, EDGES AS <edge_alias>];
 *
 * @author zhangtao
 */
public class GetSubgraphOperation implements GetSubgraphStatementBuilder.GetSubgraphStatement {
    private NebulaSession nebulaSession;
    private boolean withProp = true;
    private Integer steps;
    private List<Object> vidList = new ArrayList<>();
    private String reference;
    private String vertexAlias = "nodes";
    private String edgeAlias = "relationships";
    private WhereStatement where;
    private List<String> inEdgeTypeList = new ArrayList<>();
    private List<String> outEdgeTypeList = new ArrayList<>();
    private List<String> bothEdgeTypeList = new ArrayList<>();

    private GetSubgraphOperation() {
    }

    private GetSubgraphOperation(NebulaSession nebulaSession) {
        this.nebulaSession = nebulaSession;
    }

    protected static GetSubgraphStatementBuilder.GetSubgraphStart create() {
        return new GetSubgraphOperation();
    }

    protected static GetSubgraphStatementBuilder.GetSubgraphStart create(NebulaSession nebulaSession) {
        return new GetSubgraphOperation(nebulaSession);
    }

    @Override
    public GetSubgraphStatementBuilder.GetSubgraphWithPropOut withProp(boolean withProp) {
        this.withProp = withProp;
        return this;
    }

    @Override
    public GetSubgraphStatementBuilder.GetSubgraphStepOut steps(int steps) {
        this.steps = steps;
        return this;
    }

    @Override
    public GetSubgraphStatementBuilder.GetSubgraphFromOut from(Object... vids) {
        this.vidList.addAll(Arrays.stream(vids).collect(Collectors.toList()));
        return this;
    }

    @Override
    public GetSubgraphStatementBuilder.GetSubgraphFromOut fromPipe(String reference) {
        this.reference = reference;
        return this;
    }

    @Override
    public GetSubgraphStatementBuilder.GetSubgraphEdgeOut in(String... edgeType) {
        this.inEdgeTypeList.addAll(Arrays.stream(edgeType).collect(Collectors.toList()));
        return this;
    }

    @Override
    public GetSubgraphStatementBuilder.GetSubgraphEdgeOut out(String... edgeType) {
        this.outEdgeTypeList.addAll(Arrays.stream(edgeType).collect(Collectors.toList()));
        return this;
    }

    @Override
    public GetSubgraphStatementBuilder.GetSubgraphEdgeOut both(String... edgeType) {
        this.bothEdgeTypeList.addAll(Arrays.stream(edgeType).collect(Collectors.toList()));
        return this;
    }

    @Override
    public GetSubgraphStatementBuilder.GetSubgraphYieldOut yield(String vertexAlias, String edgeAlias) {
        this.vertexAlias = vertexAlias;
        this.edgeAlias = edgeAlias;
        return this;
    }

    @Override
    public GetSubgraphStatementBuilder.GetSubgraphWhereOut and(String key, String operator, Object value) {
        if (this.where == null) {
            this.where = WhereStatement.create();
        }
        this.where.and(key, operator, value);
        return this;
    }

    @Override
    public GetSubgraphStatementBuilder.GetSubgraphWhereOut or(String key, String operator, Object value) {
        if (this.where == null) {
            this.where = WhereStatement.create();
        }
        where.or(key, operator, value);
        return this;
    }

    @Override
    public String build() {
        StringBuilder builder = new StringBuilder("GET SUBGRAPH ");

        if (withProp) {
            builder.append(" WITH PROP ");
        }

        //拼接steps
        if (steps != null) {
            builder.append(steps);
            builder.append(" STEPS ");
        }

        //拼接vid
        if (!CollectionUtils.isEmpty(vidList)) {
            builder.append(vidList.stream().map(vid ->
                    {
                        if (vid instanceof Integer) {
                            return String.valueOf(vid);
                        } else {
                            return String.format(QUOTED_LITERAL_FORMAT, vid);
                        }
                    }
            )
                    .collect(Collectors.joining(",", " FROM ", " ")));
        }

        //拼接引用变量
        if (StringUtils.isNotEmpty(this.reference)) {
            builder.append(reference);
        }

        //拼接入边类型
        if (!CollectionUtils.isEmpty(inEdgeTypeList)) {
            builder.append(inEdgeTypeList.stream().collect(Collectors.joining(",", " IN ", " ")));
        }

        //拼接入边类型
        if (!CollectionUtils.isEmpty(outEdgeTypeList)) {
            builder.append(outEdgeTypeList.stream().
                    collect(Collectors.joining(",", " OUT ", " ")));
        }

        //拼接双边类型
        if (!CollectionUtils.isEmpty(bothEdgeTypeList)) {
            builder.append(bothEdgeTypeList.stream().
                    collect(Collectors.joining(",", " BOTH ", " ")));
        }

        //拼接where条件
        if (where != null) {
            builder.append(" WHERE ");
            builder.append(where);
        }

        //拼接返回字段
        builder.append(StrFormatter.format(" YIELD VERTICES AS {},EDGES AS {} ", vertexAlias, edgeAlias));

        return builder.toString();
    }

    @Override
    public ResultSet excute() {
        return nebulaSession.query(this.build());
    }
}
